﻿using Ganweisoft.IoTCenter.Module.MediaPlatform.Models;
using IoTCenter.Utilities.Extensions;
using IoTCenter.Utilities.Models;
using Microsoft.Extensions.Logging;
using Onvif.WebApi.Common;
using Onvif.WebApi.Services;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Serialization;

namespace Onvif.WebApi.ServicesImpl
{
    public class UdpClientService : IUdpClientService
    {
        private readonly ILogger<UdpClientService> _logger;
        public UdpClientService(ILoggerFactory loggerFactory)
        {
            _logger = loggerFactory.CreateLogger<UdpClientService>();
        }

        /// <summary>
        /// 网络接口列表
        /// </summary>
        public async Task<IEnumerable<NameValueObject>> GetNetworkInterfaceSimpleList()
        {
            var allNetworkInterfaces = NetworkInterface.GetAllNetworkInterfaces();
            if (allNetworkInterfaces.IsEmpty())
            {
                return Enumerable.Empty<NameValueObject>();
            }

            var results = new List<NameValueObject>();
            foreach (var adapter in allNetworkInterfaces)
            {
                if (!IsValidAdapter(adapter))
                {
                    continue;
                }

                var result = new NameValueObject()
                {
                    Name = adapter.Name
                };

                var unicastAddresses = adapter.GetIPProperties().UnicastAddresses.Select(x => x.Address);
                foreach (var address in unicastAddresses)
                {
                    if (address.AddressFamily != AddressFamily.InterNetwork)
                    {
                        continue;
                    }

                    result.Value = address.ToString();

                    results.Add(result);
                }
            }

            await Task.CompletedTask;

            return results;
        }

        /// <summary>
        /// 获取Udp客户端
        /// </summary>
        public UdpClient GetUdpClient(string localIp)
        {
            var allNetworkInterfaces = NetworkInterface.GetAllNetworkInterfaces();
            if (allNetworkInterfaces.IsEmpty())
            {
                return default;
            }

            foreach (var adapter in allNetworkInterfaces)
            {
                if (!IsValidAdapter(adapter))
                {
                    continue;
                }

                var unicastAddresses = adapter.GetIPProperties().UnicastAddresses.Select(x => x.Address);
                foreach (var address in unicastAddresses)
                {
                    if (address.AddressFamily != AddressFamily.InterNetwork)
                    {
                        continue;
                    }

                    if (address.ToString() != localIp)
                    {
                        continue;
                    }

                    return new UdpClient(new IPEndPoint(address, 0))
                    {
                        EnableBroadcast = true
                    };
                }
            }

            return default;
        }

        private bool IsValidAdapter(NetworkInterface adapter) =>
            (adapter.NetworkInterfaceType == NetworkInterfaceType.Ethernet || adapter.NetworkInterfaceType == NetworkInterfaceType.Wireless80211) &&
            adapter.OperationalStatus == OperationalStatus.Up &&
            adapter.Supports(NetworkInterfaceComponent.IPv4);

        public async Task<int> SendProbeMessages(UdpClient udpClient, string remoteIP, string messageId)
        {
            var multicastEndpoint = new IPEndPoint(IPAddress.Parse(remoteIP), 3702);

            var probeMessagewithguid = string.Format(Constant.PROBE_MESSAGE, messageId.ToString());

            var datagram = Encoding.ASCII.GetBytes(probeMessagewithguid);

            return await udpClient.SendAsync(datagram, multicastEndpoint);
        }

        public async Task<IEnumerable<OnvifDeviceDiscoveryRecord>> ReceiveDiscoverMessages(UdpClient udpClient, string messageId, CancellationToken cancellationToken)
        {
            var results = new List<OnvifDeviceDiscoveryRecord>();
            await foreach (var response in ReceiveResultsAsync(udpClient, cancellationToken))
            {
                var discoveredDevice = ProcessResponse(response, messageId);
                if (discoveredDevice is null || discoveredDevice.XAddresses.IsEmpty())
                {
                    continue;
                }

                results.Add(discoveredDevice);
            }

            return results;
        }

        private XmlProbeResponse DeserializeResponse(string xml)
        {
            var serializer = new XmlSerializer(typeof(XmlProbeResponse));
            var settings = new XmlReaderSettings();
            using var textReader = new StringReader(xml);
            using var xmlReader = XmlReader.Create(textReader, settings);
            return serializer.Deserialize(xmlReader) as XmlProbeResponse;
        }

        private OnvifDeviceDiscoveryRecord ProcessResponse(UdpReceiveResult response, string messageId)
        {
            var strResponse = Encoding.UTF8.GetString(response.Buffer);
            var xmlResponse = DeserializeResponse(strResponse);
            if (IsFromProbeMessage(messageId, xmlResponse)
                && xmlResponse!.Body.ProbeMatches.Any()
                && !string.IsNullOrEmpty(xmlResponse.Body.ProbeMatches[0].Scopes))
            {
                return CreateDevice(xmlResponse.Body.ProbeMatches[0], response.RemoteEndPoint);
            }

            return null;
        }

        private OnvifDeviceDiscoveryRecord CreateDevice(ProbeMatch probeMatch, IPEndPoint remoteEndpoint) =>
        new(
            ConvertToList(probeMatch.XAddrs),
            ParseModelFromScopes(probeMatch.Scopes),
            ParseMfrFromScopes(probeMatch.Scopes),
            remoteEndpoint.Address.ToString()
        );

        private string ParseMfrFromScopes(string scopes)
        {
            var scopesArray = scopes.Split();
            var nameQuery = scopesArray.Where(scope => scope.Contains("name/")).ToArray();
            var mfrQuery = scopesArray.Where(scope => scope.Contains("mfr/") || scope.Contains("manufacturer/"))
                .ToArray();
            if (mfrQuery.Length > 0)
            {
                var mfrMatch = Regex.Match(mfrQuery[0], Constant.PATTERN, RegexOptions.None,
                    TimeSpan.FromMilliseconds(RegexProcessingTimeoutInMs));
                return Uri.UnescapeDataString(mfrMatch.Groups[6].Value);
            }

            if (nameQuery.Length <= 0)
            {
                return string.Empty;
            }

            var nameMatch = Regex.Match(nameQuery[0], Constant.PATTERN, RegexOptions.None,
                TimeSpan.FromMilliseconds(RegexProcessingTimeoutInMs));
            var mfr = Uri.UnescapeDataString(nameMatch.Groups[6].Value);
            if (mfr.Contains(' '))
            {
                mfr = mfr.Split()[0];
            }

            return mfr;
        }

        private IEnumerable<string> ConvertToList(string spacedListString)
        {
            var strings = spacedListString.Split();
            foreach (var str in strings)
            {
                yield return str.Trim();
            }
        }

        private const int RegexProcessingTimeoutInMs = 500;

        private string ParseModelFromScopes(string scopes)
        {
            var model = Regex.Match(scopes, "(?<=hardware/).*?(?= )", RegexOptions.None,
                TimeSpan.FromMilliseconds(RegexProcessingTimeoutInMs)).Value;

            return Uri.UnescapeDataString(model);
        }

        private bool IsFromProbeMessage(string messageId, XmlProbeResponse response) =>
        response?.Header.RelatesTo.Contains(messageId) ?? false;

        private async IAsyncEnumerable<UdpReceiveResult> ReceiveResultsAsync(UdpClient udpClient, [EnumeratorCancellation] CancellationToken cancellationToken)
        {
            while (!cancellationToken.IsCancellationRequested)
            {
                UdpReceiveResult? response = null;
                try
                {
                    response = await udpClient.ReceiveAsync(cancellationToken);
                }
                catch (Exception ex) when (ex is TaskCanceledException or OperationCanceledException)
                {
                    //Console.WriteLine(ex.Message);
                }
                catch (Exception)
                {
                    //Console.WriteLine(ex.Message);
                }

                if (response.HasValue)
                {
                    yield return response.Value;
                }
            }
        }
    }
}
